package com.gzz.socket.nio;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 客户端：
 */
public class NIOClientTest {
    private final static Logger log = LogManager.getLogger(NIOClientTest.class);
    private final static String SERV_HOST=  "localhost";
    private final static int SERV_PORT=  9988;
    private static final AtomicInteger handleDataNo =new AtomicInteger(0);

    public static void main(String[] args) throws IOException, InterruptedException {
        String clentId = "clent2_";
        // 初始化客户端Socket
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        // 注册连接成功事件监听
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        // 发起连接
        socketChannel.connect(new InetSocketAddress(SERV_HOST, SERV_PORT));
        while (selector.select() > 0) {
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            if (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();;
                if (!key.isValid()) {
                    continue;
                }
                //connect事件：表示连接通道连接就绪或者发生了错误，会被加到ready 集合中
                // 所以这个事件发生的时候不能简单呢的认为连接成功，要使用finishConnect判断下，如果连接失败，会抛出异常
                // 当连接到服务端成功时，变更监听为 可写
                if (key.isValid() && key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    if(channel.finishConnect()) {
                        log.info("连接成功。。 Connect successfully");
                        key.interestOps(SelectionKey.OP_WRITE);
                    }else{
                        log.info("连接失败。。Connect Faild");
                    }
                }

                // 写入心跳信息，然后变更监听为 可读，即等待读取服务端 回执
                if (key.isWritable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    // byte[] heatBeat = {(byte) 0xff,(byte) 0xFF, (byte) 0xbb};
                    // String heatBeat = "im100000001";
                    String heatBeat = clentId + handleDataNo.incrementAndGet() ;
                    // channel.write(ByteBuffer.wrap(heatBeat.getBytes()));
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    buf.clear();
                    buf.put(heatBeat.getBytes());
                    buf.flip();
                    while (buf.hasRemaining()) {
                        int len = socketChannel.write(buf);
                        if(len < 0) {
                            throw new EOFException();
                        }
                        // 如果返回0，表示缓冲区满，那么注册WRITE事件，缓冲区不满的情况下，就会触发WRITE事件，在那时候再写入，可以避免不要的消耗
                        if (len == 0) {
                            key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
                            selector.wakeup();
                            break;

                        }
                    }
                    // channel.write(ByteBuffer.wrap("heartBeatClient_1__END".getBytes()));
                    key.interestOps(SelectionKey.OP_READ);
                }
                // 读取服务端回执后，然后变更监听为 可写，准备下一次写入心跳信息
                if (key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len = channel.read(buffer);
                    buffer.flip();
                    if(len < 0){
                        System.out.println("服务端{"+ channel.getRemoteAddress()+"} 关闭通道了。。。 ");
                        channel.close();
                        break;
                    }else {
                        System.out.println("接收到{" + len + "}：" + new String(buffer.array(), 0, buffer.limit()));
                        key.interestOps(SelectionKey.OP_WRITE);
                        TimeUnit.MILLISECONDS.sleep(5000);
                    }
                    //Thread.sleep(2000);
                }
                //

            }
        }
    }
}
